﻿#light
namespace AssemblyMonkey
open System
open System.IO
open System.Drawing
open System.Windows.Forms

open Mono.Cecil
open Cecil.Decompiler
open Cecil.Decompiler.Languages

module Main =
    let printList f s tw l =
        l |> Seq.iteri (fun i x -> 
            if i = 0 then 
                Printf.fprintf tw "%a" f (Seq.hd l) 
            else
                Printf.fprintf tw "%s%a" s f x )

    type Mono.Cecil.MethodDefinition with
        member meth.FullName
            with get() =
                let sw = new StringWriter()
                Printf.fprintf sw "%s (%a) : %s" 
                    meth.Name 
                   (printList (fun tw (p:ParameterDefinition) -> Printf.fprintf tw "%s" p.ParameterType.Name) ", ") 
                   (seq { for p in meth.Parameters -> p }) 
                   meth.ReturnType.ReturnType.Name
                sw.ToString()
        member meth.IsPropertyMethod(props: seq<PropertyDefinition>) =
            (meth.Name.StartsWith("get_") || meth.Name.StartsWith("set_") && 
             props 
             |> Seq.exists (fun prop -> prop.Name = meth.Name.Substring(4, meth.Name.Length - 4))) 

    type LazyTreeNode() =
        inherit TreeNode()

    let nodeImageOfDef (def:obj) =
        match def with
        | :? AssemblyDefinition -> "Assembly16x16.png"
        | :? ModuleDefinition -> "Module16x16.png"
        | :? TypeDefinition -> "Class16x16.png"
        | :? PropertyDefinition -> "Properties16x16.png"
        | :? MethodDefinition as meth when meth.IsConstructor -> "ctor16x16.png"
        | :? MethodDefinition -> "Method16x16.png"
        | :? FieldDefinition -> "Field16x16.png"
        | :? EventDefinition -> "Event16x16.png"
        | :? seq<TypeDefinition> -> "namespace16x16.png"
        | _ -> failwith "unknown node type"

    let openFile (treeView: TreeView) (x: string) =
        let assemNode = treeView.Nodes.Add x
        assemNode.Tag <- AssemblyFactory.GetAssembly(x)
        assemNode.ImageKey <- nodeImageOfDef assemNode.Tag
        assemNode.Nodes.Add(new LazyTreeNode()) |> ignore


    let handleExpand (node: TreeNode) =
        if node.Nodes.Count > 0 && (node.Nodes.[0] :? LazyTreeNode) then 
        
            let addNode (name:string) def expandable =
                let node = node.Nodes.Add(name)
                node.Tag <- def
                node.ImageKey <- nodeImageOfDef def
                if expandable then 
                    node.Nodes.Add(new LazyTreeNode()) |> ignore

            node.Nodes.Clear()
            match node.Tag with
            | :? AssemblyDefinition as assem -> 
                for modul in assem.Modules do
                    addNode modul.Name modul true
            | :? ModuleDefinition as modul ->
                let types = seq { for t in modul.Types -> t}
                let map = types |> Seq.fold (fun map t ->
                    if Map.exists (fun key _ -> key = t.Namespace) map then
                        let types = t :: map.[t.Namespace]
                        let map = map.Remove t.Namespace 
                        map.Add(t.Namespace, types)
                    else 
                        Map.add t.Namespace [t] map) Map.empty
                map |> Map.iter (fun ns types -> addNode ns types true)
            | :? seq<TypeDefinition> as types ->
                for typ in types do
                    addNode typ.Name typ true
            | :? TypeDefinition as typ ->
                let props = seq { for p in typ.Properties -> p }
                let methods = 
                    seq { for m in typ.Methods -> m }
                    |> Seq.filter (fun meth -> not (meth.IsPropertyMethod props))
                for meth in methods do
                    addNode meth.FullName meth false
                for ctor in typ.Constructors do
                    addNode ctor.FullName ctor false
                for prop in typ.Properties do
                    addNode prop.Name prop true
                for field in typ.Fields do
                    addNode field.Name field false
                for event in typ.Events do
                    addNode event.Name event false
                for nestedType in typ.NestedTypes do
                    addNode nestedType.Name nestedType true
            | :? PropertyDefinition as prop -> 
                let ifNotNull (meth:MethodDefinition) =
                    match meth with
                    | null -> ()
                    | _ -> addNode meth.FullName meth false
                ifNotNull prop.GetMethod
                ifNotNull prop.SetMethod
            | _ -> ()

    let cs1 = CSharp.GetLanguageSupport (CSharpOptimization.V1)

    let handleSelect (node: TreeNode) (output:WebBrowser) =
        match node.Tag with
        | :? MethodDefinition as meth -> 
            let sw = new StringWriter()
            let doWriting() =
                use formatter = new HtmlFormatter(sw)
                let langWriter = cs1.GetWriter(formatter)
                let ast = meth.Body.Decompile(cs1)
                langWriter.Write(ast)
            doWriting()
            output.DocumentText <- sw.ToString()
        | _ -> ()


    type Fake() = class
        end

    let images = [ "Assembly16x16.png"; "Event16x16.png";
                   "Class16x16.png"; "Module16x16.png";
                   "Properties16x16.png"; "Method16x16.png";
                   "namespace16x16.png"; "ctor16x16.png";
                   "Field16x16.png"; ]

    let createForm assems = 
        // create controls and wire them up
        let form = new Form(Text = "Assembly Monkey - The Cecil.Decompiler front-end", Width = 600, Height = 800)
        let horiSplitter = new SplitContainer(Dock = DockStyle.Fill, 
                                              Orientation = Orientation.Horizontal,
                                              Width = 800,
                                              SplitterDistance = 750)
        let vertSplitter = new SplitContainer(Dock = DockStyle.Fill,
                                              Width = 600,
                                              SplitterDistance = 300)
        let assemblyTree = new TreeView(Dock = DockStyle.Fill)
        vertSplitter.Panel1.Controls.Add(assemblyTree)
        let mainOutput = new WebBrowser(Dock = DockStyle.Fill)
        let littleOutput = new WebBrowser(Dock = DockStyle.Fill)
        vertSplitter.Panel2.Controls.Add(mainOutput)
        horiSplitter.Panel1.Controls.Add(vertSplitter)
        horiSplitter.Panel2.Controls.Add(littleOutput)
        form.Controls.Add(horiSplitter)
        
        // configure the images and icons
        let assm = typeof<Fake>.Assembly
        
        form.Icon <- new Icon(assm.GetManifestResourceStream("monkey.ico"))

        assemblyTree.ImageList <- new ImageList()
        let addImage filename =
            let stream =  assm.GetManifestResourceStream(filename)
            assemblyTree.ImageList.Images.Add(filename, Image.FromStream(stream))
        List.iter addImage images
        
        assemblyTree.BeforeExpand.Add(fun eventArgs -> handleExpand eventArgs.Node)
        assemblyTree.BeforeSelect.Add(fun eventArgs -> handleSelect eventArgs.Node mainOutput)
        
        mainOutput.Navigating.Add(fun ea -> 
            //ea.Cancel <- true 
            MessageBox.Show (ea.Url.ToString()) |> ignore)
//        mainOutput.Navigated.Add(fun ea -> 
//            //ea.Cancel <- true 
//            MessageBox.Show (ea.Url.ToString()) |> ignore)

        let openAssembly _ _ =
            let openFileDialog = new OpenFileDialog(Multiselect = false,
                                                    Filter = "Assembly (*.dll)|*.dll|All Files|*.*")
            if openFileDialog.ShowDialog() = DialogResult.OK then
                openFile assemblyTree openFileDialog.FileName

        let fileItems = [| new MenuItem("Open Assembly ...", openAssembly) |]
        let file = new MenuItem("File", fileItems)
        form.Menu <- new MainMenu([|file|])
        
        List.iter (openFile assemblyTree) assems
        
        form
        
    [<STAThread; EntryPoint>]
    let main (args: string[]) =
        let form = createForm (List.of_array args)
        Application.Run form
        0